using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using Polymod.TemplateBuilders;

namespace Polymod
{
    public class ModelFactory : IModelFactory
    {
        private readonly Dictionary<Type, IModelTemplate> _modelTemplates = new Dictionary<Type, IModelTemplate>();
        private readonly object _syncLock = new object();
        //NOTE: The factory will contain one and only one template builder responsible for building all the ModelTemplates.
        private IModelTemplateBuilder _builder;
        private AssemblyBuilder _asmBuilder;
        private ModuleBuilder _moduleBuilder;

        public ModelFactory(IModelTemplateBuilder builder)
        {
            if (builder == null) throw new System.ArgumentNullException("builder");
            _builder = builder;
            var aName = new AssemblyName("ModelsAssembly");
            _asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);

            // For a single-module assembly, the module name is usually
            // the assembly name plus an extension.
            _moduleBuilder = _asmBuilder.DefineDynamicModule(aName.Name, false);
        }

        public IModel<T> GetModel<T>(T value)
        {
            return (IModel<T>)GetModel(null, value);
        }

        public IModel GetModel(IParentModelInfo parent, object value)
        {
            if (value == null) throw new System.ArgumentNullException("value");
            IModelTemplate template;
            if (!_modelTemplates.TryGetValue(value.GetType(), out template))
            {
                //Lock Here because the template builder may only add a new type at a time to an assembly.
                lock (_syncLock)
                {
                    if (!_modelTemplates.TryGetValue(value.GetType(), out template))
                    {
                        if (!IsModelCandidate(value.GetType())) throw new InvalidOperationException(string.Format("Cannot create model for type {0}. Is is not a valid model candidate.", value.GetType()));

                        var parameters = new ModelTemplateParameters() { ModelType = value.GetType(), ModuleBuilder = _moduleBuilder, ModelFactory = this };
                        template = this.TemplateBuilder.BuildTemplate(parameters);
                        _modelTemplates[value.GetType()] = template;
                    }
                }
            }
            var modelState = new Dictionary<string, object>();
            modelState[Constants.ParentModelInfo] = parent;

            return template.CreateModel(value, modelState);
        }

        public virtual bool IsModelCandidate(Type type)
        {
            if (type == null) throw new System.ArgumentNullException("type");
            return type.IsClass && type != typeof(string);
        }

        public IModelTemplateBuilder TemplateBuilder
        {
            get { return _builder; }
        }
    }
}
